package com.ewei.web.common.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
/**
 * randomAccessFile for buffer
 * 1.对RandomAccessFile增加缓冲区提高性能,增加readBuffer,writeBuffer方法
 *  by 2015
 * 2.增加readBufferLine方法读取字符串,
 * 	   原因是原RandomAccessFile读取字符没有进行解码,只是单纯地char c = (char)b转换,
 *	   改进方法是借鉴sun.nio.cs.StreamDecoder内的implyRead方法对字节数组解码为字符数组(详见openjdk)
 *  by 2016
 * @author David
 *
 */
public class BufferedRandomAccessFile extends RandomAccessFile{
	
	private static final String DEFAULT_CHARSET = "UTF-8";
	
	private static final int DEFAULT_BUFFERSIZE = 8921;
	
	private static final int DEFAULT_CHAR_BUFFERSIZE = 5120;
	
	private volatile byte[] buffer;
	
	private volatile CharBuffer charBuffer;
	
	private long currentPos = -1l;
	
	private long stPos = -1l,endPos = -1l;
	
	private boolean isWrite = false;
	
	public BufferedRandomAccessFile(File file, String mode)
			throws FileNotFoundException {
		super(file, mode);
		buffer = new byte[DEFAULT_BUFFERSIZE];
		this.seek(0);
		currentPos = 0l;
	}
	public BufferedRandomAccessFile(String fileName,String mode)
			throws FileNotFoundException {
		super(fileName, mode);
		buffer = new byte[DEFAULT_BUFFERSIZE];
		this.seek(0);
		currentPos = 0l;
	}
	private void fill(long pos) throws IOException{//fill the buffer
		int offset = 0;
		if(pos == stPos){
			return;
		}else{
			offset = super.read(buffer);
		}
		if(offset<=-1) stPos = endPos = -1l;
		else{
			stPos = pos;
			endPos = stPos + offset;
		}
		super.seek(pos);//return the position
	}
	public void seek(long pos){
		try {
			currentPos = pos;
			flush();//flush
			if(pos>-1) super.seek(pos);
			fill(pos);
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println(pos);
			throw new RuntimeException("there is a wrong in seeking file!");
		}
	}
	//flush the buffer
	public void flush()throws IOException{
		if(isWrite&(stPos|endPos)>=0){//if the buffer is written,write the buff to the file
			super.write(buffer, 0,(int)(endPos-stPos));
			isWrite = false;
		}
		stPos = endPos = -1l;
	}
	//read from buffer
	public synchronized int readBuffer() throws IOException{
		long pos = currentPos;
		if(pos>=stPos&&pos<endPos){ //in the buffer
			currentPos++;
			return buffer[(int)(pos-stPos)]&0xff;
		}else{
			seek(endPos);
			if((stPos|endPos) == -1l){
				return -1;
			}
			currentPos++;
			return buffer[0]&0xff;
		}
	}
	public synchronized int readBuffer(byte[] b) throws IOException{
		return readBuffer(b,0,b.length);
	}
	public synchronized int readBuffer(byte[] b,int off,int len) throws IOException{
		long pos = currentPos;
		int avail = (int)(endPos - pos);
		if(avail<=0){ //no rest size in buffer
			seek(pos);  
			if((stPos|endPos) == -1l) return -1;
			avail = (int)(endPos - pos);
		}
		int cut = avail<len?avail:len;
		System.arraycopy(buffer, (int) (pos - stPos), b, off, cut);
		currentPos+=cut;
		while(cut<len){
			seek(endPos);
			if((stPos|endPos) != -1l){
				avail = (int)(endPos - stPos);
				int extra = (avail<len-cut)?avail:len-cut;
				System.arraycopy(buffer,0, b, off+cut,extra); 
				cut+=extra;
				currentPos+=extra;
			}else{
				break;
			}
		}
		return cut;
	}
	public synchronized void writeBuffer(int b) throws IOException{
		long pos = currentPos;
		if(pos>=stPos&&pos<stPos+buffer.length){ //if in the buffer size
			currentPos++;
			buffer[(int)(pos-stPos)] = (byte)b;//overlap the buffer
			if(pos==endPos)//enlarge the end position
				endPos++;
		}else{
//			flush();
			seek(pos);
			if((stPos|endPos) == -1l){
				stPos = pos;
				endPos = stPos+1;
			}
			currentPos++;
			buffer[0] = (byte)b;
		}
		isWrite = true;//buffer is changed
	}
	public synchronized void writeBuffer(byte[] b) throws IOException{
		writeBuffer(b,0,b.length);
	}
	public synchronized void writeBuffer(byte[] b,int off,int len) throws IOException{
		long pos = currentPos;
		int avail = (stPos|endPos)==-1l?-1:(int)(buffer.length - pos+stPos);
		if(avail<=0){
			seek(pos);
			if((stPos|endPos) == -1l){
				stPos = pos;
				endPos = stPos+1;
			}
			avail = buffer.length;
		}
		int write = len<avail?len:avail;
		System.arraycopy(b,off,buffer,(int) (pos - stPos),write);
		if(pos+write>= endPos) endPos = pos+write;
		isWrite = true;//buffer is changed
		currentPos+=write;
		while(write<len){
			long nPos = stPos+buffer.length;
			seek(nPos);
			int extra = (buffer.length<len-write)?buffer.length:len-write;
			System.arraycopy(b,off+write, buffer, 0,extra); 
			if((stPos|endPos) == -1l){
				stPos = nPos;
				endPos = stPos+extra;
			}
			isWrite = true;//buffer is changed
			write+=extra;
			currentPos+=extra;
		}
	}
	/**
	 *针对字符流的行读取方法 
	 */
	public synchronized String readBufferLine() throws IOException{
		if(charBuffer == null){
			charBuffer = CharBuffer.allocate(DEFAULT_CHAR_BUFFERSIZE);
			charBuffer.limit(0);//一开始limit设置为零，没有数据
		}
		StringBuffer input = new StringBuffer();
		int lef = 0;
		boolean eol = false;
		while(!eol){
			if(!charBuffer.hasRemaining()){
				lef = implyRead(charBuffer);
				if(lef<0){
					eol = true;
				}
			}
			while(charBuffer.hasRemaining()&&!eol){
				char c = charBuffer.get();
				switch(c){
					case '\n':
						eol = true;
						break;
					case '\r':
						continue;
					default:
						input.append(c);
						break;
				}
			}
		}
		if(input.length() == 0&&(lef == -1)){
			return null;
		}
		return input.toString();
	}
	private int implyRead(CharBuffer buf) throws IOException{
		if((stPos|endPos) == -1l){
			return -1;
		}
		//position指针从0开始读取写数据
		buf.rewind();
		//limit设置最大,尽可能地接受解码后的数据
		buf.limit(DEFAULT_CHAR_BUFFERSIZE);
		int st =(int)(currentPos-stPos);
		int end = (int)(endPos-currentPos);
		ByteBuffer in = ByteBuffer.wrap(buffer,st,end);
		CharsetDecoder decoder = 
				Charset.forName(DEFAULT_CHARSET).newDecoder();
		boolean endOfInput = false;
		CoderResult result = decoder.decode(in, buf, endOfInput);
		currentPos +=(in.position() - st);
		if(result.isUnderflow()){
			if(currentPos >= endPos){
				seek(currentPos);
			}else if(buf.position()<=0){
				//因为endOfInput等于false的原因,虽然输入区未空但是无输出
				if(in.hasRemaining()){
					seek(currentPos);
				}
			}
		}else if(result.isError()||result.isMalformed()){
			return -1;
		}
		int len = buf.position();
		//limit设置为接受数据的长度
		buf.limit(len);
		//position指针从0开始读取
		buf.rewind();
		return len;
	}
	@Override
	public void close() throws IOException {
		if(null != charBuffer){
			charBuffer.clear();
		}
		super.close();
	}
}
